package com.ruoyi.finance.utils;

/**
 * @Author: tjk
 * @Date: 2025/3/10 11:15
 * @Description:
 **/
public class InvoiceNumberGenerator {
    // 起始时间戳（可根据需求调整）
    private final static long EPOCH = 1609459200000L; // 2021-01-01 00:00:00 UTC

    // 各部分位数分配
    //分别表示时间戳
    private final static int TIMESTAMP_BITS = 41;
    //工作节点 ID
    private final static int WORKER_ID_BITS = 10;
    //序列号部分的位数。
    private final static int SEQUENCE_BITS = 12;

    // 最大值计算
    //工作节点 ID 的最大值，通过位运算计算得出。
    private final static long MAX_WORKER_ID = ~(-1L << WORKER_ID_BITS);
    //序列号的最大值，通过位运算计算得出
    private final static int MAX_SEQUENCE = ~(-1 << SEQUENCE_BITS);

    // 位移量
    //分别表示工作节点 ID 和时间戳部分的位移量，用于将各部分组合成最终的 ID。
    private final static int WORKER_ID_SHIFT = SEQUENCE_BITS;
    private final static int TIMESTAMP_SHIFT = WORKER_ID_BITS + SEQUENCE_BITS;
    //工作节点 ID，用于区分不同的节点。
    private final long workerId;
    //序列号，用于在同一毫秒内生成不同的 ID。
    private volatile long sequence = 0L;
    //上一次生成 ID 的时间戳，用于判断时钟是否回拨。
    private volatile long lastTimestamp = -1L;

    //构造方法接收一个工作节点 ID 作为参数，并进行合法性检查。如果工作节点 ID 超出范围，则抛出 IllegalArgumentException 异常。
    public InvoiceNumberGenerator(long workerId) {
        if (workerId < 0 || workerId > MAX_WORKER_ID) {
            throw new IllegalArgumentException("Worker ID must be between 0 and " + MAX_WORKER_ID);
        }
        this.workerId = workerId;
    }

    public String generate() {
        // 获取当前时间戳：通过 System.currentTimeMillis() 方法获取当前时间戳。
        long timestamp = System.currentTimeMillis();

        //关键字保证在同一时刻只有一个线程可以生成 ID，避免并发问题。
        synchronized (this) {
           // 如果当前时间戳小于上一次生成 ID 的时间戳，说明时钟发生了回拨，抛出 RuntimeException 异常。
            if (timestamp < lastTimestamp) {
                throw new RuntimeException("Clock moved backwards. Refusing to generate ID");
            }
            //如果当前时间戳等于上一次生成 ID 的时间戳，则将序列号加 1，并通过位运算确保序列号不超过最大值。如果序列号达到最大值，则调用 waitNextMillis 方法等待下一毫秒。
            if (timestamp == lastTimestamp) {
                sequence = (sequence + 1) & MAX_SEQUENCE;
                if (sequence == 0) {
                    timestamp = waitNextMillis(timestamp);
                }
            } else {
                sequence = 0L;
            }
            //更新 lastTimestamp 为当前时间戳。
            lastTimestamp = timestamp;
        }
       //将时间戳、工作节点 ID 和序列号通过位运算组合成最终的 ID。
        long id = ((timestamp - EPOCH) << TIMESTAMP_SHIFT)
                | (workerId << WORKER_ID_SHIFT)
                | sequence;

        return String.valueOf(id);
    }

    //该方法用于等待下一毫秒的到来，通过不断获取当前时间戳，直到时间戳大于当前时间戳为止。
    private long waitNextMillis(long currentTimestamp) {
        long timestamp = System.currentTimeMillis();
        while (timestamp <= currentTimestamp) {
            timestamp = System.currentTimeMillis();
        }
        return timestamp;
    }

    // 获取实例ID的示例方法（根据部署环境实现）
    //该方法用于从环境变量中获取工作节点 ID，适用于分布式环境下的部署。
    public static long getWorkerIdFromEnv() {
        // 从环境变量或配置中心获取，例如Kubernetes StatefulSet的Pod序号
        String workerIdStr = System.getenv("WORKER_ID");
        return Long.parseLong(workerIdStr);
    }
}
